#include <GeomAPI_PointsToBSpline.hxx>
#include <Geom_BSplineCurve.hxx>

#include "Qt3DPicker.h"
#include "FitCurve3DHandler.h"
#include "CmdManager.h"
#include "Scene.h"
#include "SendPointHandlerBase.h"
#include "SelectHandler3D.h"
#include "OSGNodeMask.h"
#include "Operation/OpReplace.h"
#include "Operation/OpAdd.h"
#include "Spline3D.h"
#include "PointHandler.h"
#include "LineStrip3D.h"
#include "FitCurveTool.h"
#include "QtOSGWidget.h"

std::vector<osg::Vec3d> convert(const TColgp_Array1OfPnt& points);
TColgp_Array1OfPnt convert(const std::vector<osg::Vec3d>& points);
std::vector<double> convert(const TColStd_Array1OfReal& weights);
std::vector<int> convert(const TColStd_Array1OfInteger& mults);
void toACADKnots(const std::vector<double>& knots, const std::vector<int>& mults, std::vector<double>& acadKnots);

std::shared_ptr<Spline3D> makeFitCurve(INode* parent, const std::vector<osg::Vec3d>& points)
{
	try
	{
		GeomAPI_PointsToBSpline approx(convert(points), 2, 3, GeomAbs_C2, 0.002);
		Handle(Geom_BSplineCurve) curve = approx.Curve();

		auto spline = std::make_shared<Spline3D>(parent);

		int degree = curve->Degree();
		spline->setDegree(degree);

		int flag = curve->IsClosed() ? 1 : 0;
		flag |= curve->IsPeriodic() ? 2 : 0;
		spline->setFlag(flag);

		const TColgp_Array1OfPnt& poles = curve->Poles();
		std::vector<osg::Vec3d> polesV = convert(poles);
		spline->setCtrlPoints(polesV);

		const TColStd_Array1OfReal* weights = curve->Weights();
		if (weights)
		{
			std::vector<double> weightsV = convert(*weights);
			spline->setWeights(weightsV);
		}

		auto knotDist = curve->KnotDistribution();
		std::vector<double> acadKnots;
		if (knotDist == GeomAbs_PiecewiseBezier)
		{
			Standard_Integer PNum = poles.Size();
			Standard_Integer KNum = PNum - degree + 1;
			TColStd_Array1OfReal knots(1, KNum);
			for (int i = 0; i < KNum; ++i)
			{
				knots.SetValue(i + 1, i);
			}
			TColStd_Array1OfInteger mults(1, KNum);
			for (int i = 0; i < KNum; ++i)
			{
				if (i == 0 || i == KNum - 1)
				{
					mults.SetValue(i + 1, degree + 1);
				}
				else
				{
					mults.SetValue(i + 1, degree);
				}
			}
			toACADKnots(convert(knots), convert(mults), acadKnots);
		}
		else
		{
			const TColStd_Array1OfReal& knots = curve->Knots();
			const TColStd_Array1OfInteger& mults = curve->Multiplicities();
			toACADKnots(convert(knots), convert(mults), acadKnots);
		}
		spline->setKnots(acadKnots);
		return spline;
	}
	catch (Standard_Failure&)
	{
		return nullptr;
	}
}

FitCurve3DHandler::FitCurve3DHandler()
{
}

FitCurve3DHandler::~FitCurve3DHandler()
{
}

void FitCurve3DHandler::input(const QVariant& data, const QString& rawInput, CommandSource cs)
{
	auto scene = getScene();
	auto sph = getSendPointHandler();
	auto selector = qobject_cast<SelectHandler3D*>(getSelectHandler());
	if (data.type() == QVariant::Bool)
	{
		bool esc = !data.toBool();
		if (esc)
		{
			selector->clearSelection();
			clearInput();
			emit finished();
			return;
		}
	}

	if (status() == 0)
	{
		if (selector)
		{
			selector->clearSelection();
			selector->beginSelect(D3LineMask);
			prompt(1, tr("选择连续的线段: "));
		}
		else
		{
			emit finished();
		}
		return;
	}

	if (status() == 1)
	{
		if (selector && selector->done())
		{
			auto selections = selector->selectionSet();
			if (selections.size() > 1)
			{
				std::vector<CurvePoints> curvePoints;
				for (auto selection : selections)
				{
					auto ent = std::dynamic_pointer_cast<LineStrip3D>(selection);
					if (ent)
					{
						auto points = ent->getPoints();
						if (points.size() >= 2)
						{
							auto matrix = ent->getRep3D()->getWorldMatrices()[0];
							for (auto& pt : points)
								pt = pt * matrix;
							curvePoints.push_back({ ent, points });
						}
					}
				}
				selector->clearSelection();
				std::vector<osg::Vec3d> points = connectEnds(curvePoints, m_usedEnts);
				if (points.size() >= 3 && m_usedEnts.size() > 1)
				{
					INode* parent = (*m_usedEnts.begin())->getParent();
					m_spline = makeFitCurve(parent, points);
					m_spline->setColor(getCurrentColor());
					if (m_spline)
					{
						enableMouseHandler(MouseHandlerID::Manipulator);
						prompt(2, tr("删除原来的对象[是(y)/否(n)]<y>: "));
						return;
					}
					else
					{
						remark(tr("拟合样条线失败"));
					}
				}
				else
				{
					remark(tr("拟合样条线失败"));
				}
				emit finished();
			}
		}
		return;
	}

	if (status() == 2)
	{
		auto undoStack = scene->undoStack();
		if (rawInput == "Y" || rawInput == "y" || (data.type() == QVariant::Bool && data.toBool()))
		{
			// delete original objects
			std::vector<std::shared_ptr<Ent>> newEnts;
			newEnts.push_back(m_spline);
			undoStack->push(new OpReplace(std::vector<std::shared_ptr<Ent>>(m_usedEnts.begin(), m_usedEnts.end()), newEnts));
		}
		else
		{
			std::set<std::shared_ptr<INode>> newEnts2;
			newEnts2.insert(m_spline);
			undoStack->push(new OpAdd(newEnts2));
		}
		emit finished();
		return;
	}
}

FitPoints3DHandler::FitPoints3DHandler()
{
}

FitPoints3DHandler::~FitPoints3DHandler()
{
}

void FitPoints3DHandler::input(const QVariant& data, const QString& rawInput, CommandSource cs)
{
	auto scene = getScene();
	auto widget3D = getWidget();
	auto sph = getSendPointHandler();
	if (data.type() == QVariant::Bool)
	{
		bool esc = !data.toBool();
		if (esc)
		{
			if (m_line)
			{
				widget3D->pauseRenderThread();
				scene->removeChild(m_line);
				widget3D->resumeRenderThread();
			}
			clearInput();
			emit finished();
			return;
		}
	}

	if (status() == 0)
	{
		if (sph)
		{
			sph->setPointMask(CS_LBTNDOWN);
		}
		m_line = std::make_shared<LineStrip3D>(scene);
		m_line->setColor(getCurrentColor());
		widget3D->pauseRenderThread();
		scene->addChild(m_line);
		widget3D->resumeRenderThread();

		m_pointStateMachine = std::make_shared<PointHandler>();
		m_pointStateMachine->setCmdManager(cmdManager());
		m_pointStateMachine->setPrompt0(tr("指定曲线拟合点<取消>"));
		m_pointStateMachine->input(data, rawInput, cs);
		setStatus(1);
		enableMouseHandler(MouseHandlerID::PointSender);
		return;
	}

	std::vector<osg::Vec3d> points = m_line->getPoints();
	if (status() == 1)
	{
		if (data.type() == QVariant::Bool && data.toBool())
		{
			widget3D->pauseRenderThread();
			scene->removeChild(m_line);
			widget3D->resumeRenderThread();
			emit finished();
			return;
		}
		if (sph)
		{
			sph->setPointMask(CS_LBTNDOWN | CS_MOUSEMOVE);
		}
		m_pointStateMachine->input(data, rawInput, cs);
		if (m_pointStateMachine->done())
		{
			osg::Vec3d worldPt = m_pointStateMachine->getPoint();
			points.push_back(worldPt);
			points.push_back(worldPt);
			widget3D->pauseRenderThread();
			m_line->setPoints(points);
			widget3D->resumeRenderThread();
			//m_pointStateMachine->setPrompt0(tr("指定曲线拟合点<取消>"));
			m_pointStateMachine->reset();
			setStatus(2);
		}
		return;
	}

	if (status() >= 2)
	{
		if (data.type() == QVariant::Bool && data.toBool())
		{
			widget3D->pauseRenderThread();
			scene->removeChild(m_line);
			widget3D->resumeRenderThread();
			if (status() <= 3)
			{
				// 只有两个点，不能拟合曲线
				emit finished();
				return;
			}
			points.pop_back();
			widget3D->pauseRenderThread();
			m_line->setPoints(points);
			widget3D->resumeRenderThread();
			makeSpline(scene);
			emit finished();
			return;
		}

		if (cs == CS_MOUSEMOVE)
		{
			if (data.type() == QVariant::Vector3D)
			{
				auto pt = data.value<QVector3D>();
				osg::Vec3d worldPt = { pt.x(), pt.y(), pt.z() };
				points[points.size() - 1] = worldPt;
			}
			m_line->setPoints(points);
		}
		else
		{
			m_pointStateMachine->input(data, rawInput, cs);
			if (m_pointStateMachine->done())
			{
				osg::Vec3d worldPt = m_pointStateMachine->getPoint();
				points[points.size() - 1] = worldPt;
				points.push_back(worldPt);
				widget3D->pauseRenderThread();
				m_line->setPoints(points);
				widget3D->resumeRenderThread();
				//m_pointStateMachine->setPrompt0(tr("指定曲线拟合点<取消>"));
				setStatus(status() + 1);
				m_pointStateMachine->reset();
			}
		}
		return;
	}
}

void FitPoints3DHandler::makeSpline(Scene* scene)
{
	std::vector<osg::Vec3d> points = m_line->getPoints();
	m_spline = makeFitCurve(scene, points);
	m_spline->setColor(getCurrentColor());
	if (m_spline)
	{
		std::set<std::shared_ptr<INode>> newEnts2;
		newEnts2.insert(m_spline);
		auto undoStack = scene->undoStack();
		undoStack->push(new OpAdd(newEnts2));
		return;
	}
	else
	{
		remark(tr("拟合样条线失败"));
	}
}
